#include "p_lexer.h"

#include "p_error.h"
#include "p_strutils.h"

std::string token_type_to_string(const p_token_type &token_type)
{
   switch (token_type)
   {
   case TOK_IGNORE:
      return "TOK_IGNORE";
   case TOK_ADD:
      return "TOK_ADD";
   case TOK_SUB:
      return "TOK_SUB";
   case TOK_SUB_SUB:
      return "TOK_SUB_SUB";
   case TOK_DIV:
      return "TOK_DIV";
   case TOK_MUL:
      return "TOK_MUL";
   case TOK_POINT:
      return "TOK_POINT";
   case TOK_SEMICOLON:
      return "TOK_SEMICOLON";
   case TOK_DPOINT:
      return "TOK_DPOINT";
   case TOK_ADDR:
      return "TOK_ADDR";
   case TOK_EQ:
      return "TOK_EQ";
   case TOK_ASSIGN:
      return "TOK_ASSIGN";
   case TOK_LE:
      return "TOK_LE";
   case TOK_LT:
      return "TOK_LT";
   case TOK_NE:
      return "TOK_NE";
   case TOK_GE:
      return "TOK_GE";
   case TOK_GT:
      return "TOK_GT";
   case TOK_IDENT:
      return "TOK_IDENT";
   case TOK_VAR:
      return "TOK_VAR";
   case TOK_BEGIN:
      return "TOK_BEGIN";
   case TOK_END:
      return "TOK_END";
   case TOK_STRING:
      return "TOK_STRING";
   case TOK_TEMPLATE:
      return "TOK_TEMPLATE";
   case TOK_RBRO:
      return "TOK_RBRO";
   case TOK_RBRC:
      return "TOK_RBRC";
   case TOK_EBRO:
      return "TOK_EBRO";
   case TOK_EBRC:
      return "TOK_EBRC";
   case TOK_CBRO:
      return "CBRO";
   case TOK_CBRC:
      return "CBRC";
   case TOK_VBAR:
      return "TOK_VBAR";
   case TOK_COLON:
      return "TOK_COLON";
   case TOK_PROGRAM:
      return "TOK_PROGRAM";
   case TOK_SIGNED:
      return "TOK_SIGNED";
   case TOK_UNSIGNED:
      return "TOK_UNSIGNED";
   case TOK_BYTE:
      return "TOK_BYTE";
   case TOK_WORD:
      return "TOK_WORD";
   case TOK_DWORD:
      return "TOK_DWORD";
   case TOK_TBYTE:
      return "TOK_TBYTE";
   case TOK_RECORD:
      return "TOK_RECORD";

   case TOK_READ_ONLY:
      return "TOK_READ_ONLY";
   case TOK_PRECONDITION:
      return "TOK_PRECONDITION";
   case TOK_POSTCONDITION:
      return "TOK_POSTCONDITION";
   case TOK_EXPRESSION:
      return "TOK_EXPRESSION";

   case TOK_AND:
      return "TOK_AND";
   case TOK_NAND:
      return "TOK_NAND";
   case TOK_OR:
      return "TOK_OR";
   case TOK_NOR:
      return "TOK_NOR";
   case TOK_XOR:
      return "TOK_XOR";

   case TOK_POWER:
      return "TOK_POWER";
   case TOK_EXCLAMATION_MARK:
      return "TOK_EXCLAMATION_MARK";

   case TOK_FUNCTION:
      return "TOK_FUNCTION";
   case TOK_PROTO:
      return "TOK_PROTO";
   case TOK_ATTRIBUTES:
      return "TOK_ATTRIBUTES";
   case TOK_PASCAL:
      return "TOK_PASCAL";
   case TOK_NAKED:
      return "TOK_NAKED";
   case TOK_CDECL:
      return "TOK_CDECL";
   case TOK_STDCALL:
      return "TOK_STDCALL";
   case TOK_DLLIMPORT:
      return "TOK_DLLIMPORT";
   case TOK_EXTERN:
      return "TOK_EXTERN";

   case TOK_NIL:
      return "TOK_NIL";

   case TOK_EOF:
      return "TOK_EOF";
   }
   return "";
}

p_position_struct position_struct_create(const p_unsigned_int &index,
                                         const p_unsigned_int &line,
                                         const p_unsigned_int &col)
{
   p_position_struct result;
   result.index = index;
   result.line = line;
   result.col = col;
   return result;
}

p_position_struct position_struct_create(const p_position_struct &pos)
{
   return position_struct_create(pos.index, pos.line, pos.col);
}

std::string position_struct_to_string(const p_position_struct &pos)
{
   return to_string(pos.line) + " : " + to_string(pos.col) + " : "
            + to_string(pos.index);
}

p_token_struct token_struct_create()
{
   return token_struct_create(TOK_EOF);
}

p_token_struct token_struct_create(const p_token_type &type,
                                   const p_unsigned_int &index,
                                   const p_unsigned_int &line,
                                   const p_unsigned_int &col)
{
   p_token_struct result;
   result.type = type;
   result.ident = "";
   result.pos.line = line;
   result.pos.col = col;
   result.pos.index = index;
   return result;
}

p_token_struct token_struct_create(const p_token_type &type,
                                   const p_position_struct &pos)
{
   return token_struct_create(type, pos.index, pos.line, pos.col);
}

p_token_struct token_struct_create(const p_token_type &type,
                                   const std::string &ident,
                                   const p_position_struct &pos)
{
   p_token_struct result = token_struct_create(type, pos.index, pos.line,
                                               pos.col);
   result.ident = ident;
   return result;
}

p_token_struct token_struct_create(const p_token_struct &token_struct)
{
   return token_struct_clone(token_struct);
}

std::string token_struct_to_string(const p_token_struct &token)
{
   return "[" + token_type_to_string(token.type) + " (" + token.ident + ") at "
            + position_struct_to_string(token.pos) + "]";
}

p_token_struct token_struct_clone(const p_token_struct &token_struct)
{
   return token_struct_create(token_struct.type, token_struct.ident,
                              token_struct.pos);
}

std::string token_struct_list_to_string(
                                        const p_token_struct_list_ptr token_list)
{
   if (!token_list) return "";

   std::string result = "";
   p_token_struct_list::const_iterator iter = token_list->begin();
   while (iter != token_list->end()) {
      result += token_struct_to_string(*iter) + "\n";
      ++iter;
   }
   return result;
}

// -----------------------------------------------------------------------------

/**
 * This increases the current position index within the given text and returns
 * the old position.
 *
 * @param text The text which contains the given position pointer.
 * @param pos The position pointer within the current context.
 * @return The old position for further processing.
 */
inline p_position_struct inc(const std::string &text, p_position_struct_ptr pos)
{
   p_position_struct result = position_struct_create(pos->index, pos->line,
                                                     pos->col);
   if (pos->index < text.length()) {
      if (text[pos->index] == '\n') {
         pos->col = 0;
         pos->line++;
      }
      ++(pos->index);
      ++(pos->col);
   }

   return result;
}

/**
 * Ignores spaces from the given position.
 *
 * @param text The text which is scanned.
 * @param pos The current position within the text.
 */
void read_spaces(const std::string &text, p_position_struct_ptr pos)
{
   while ((pos->index < text.length()) && is_blank(text[pos->index]))
      inc(text, pos);
}

/**
 * Reads a number from the current text buffer.
 *
 * @param text The text which is scanned.
 * @param pos The current position within the text.
 */
std::string read_integer_number(const std::string &text,
                                p_position_struct_ptr pos)
{
   // TODO: For performance reasons; Implement it using a buffer and buffer the string output.
   std::string result = "";
   while ((pos->index < text.length()) && is_num(text[pos->index])) {
      result += text[pos->index];
      inc(text, pos);
   }
   return result;
}

/**
 * Continues scanning of the current identifiers. Important: Identifiers must
 * start with an alpha character, but this is not ensured by this function.
 *
 * @param text The text which is scanned.
 * @param pos The current position within the text.
 */
std::string read_identifier(const std::string &text, p_position_struct_ptr pos)
{
   // TODO: For performance reasons; Implement it using a buffer and buffer the string output.
   std::string result = "";
   while ((pos->index < text.length()) && is_alnum(text[pos->index])) {
      result += text[pos->index];
      inc(text, pos);
   }
   return result;
}

/**
 * This function reads until the end of a line. It is used for ignoring comments.
 */
void read_until_eoln(const std::string &text, p_position_struct_ptr pos)
{
   while ((pos->index < text.length()) && !is_eoln(text[pos->index]))
      inc(text, pos);
   read_spaces(text, pos);
}

std::string read_esc_sequence(const std::string &text,
                              p_position_struct_ptr pos)
{
   // TODO: Extend the list of esc-char!
   if (pos->index < text.length()) {
      char c = text[pos->index];
      switch (c)
      {
      case 'n':
         return "\n";
      case 'r':
         return "\r";
      case 't':
         return "\t";
      default:
         throw error_struct_create("unknown esc-sequence!");
      }
   } else {
      throw error_struct_create("esc-sequence error!");
   }
}

std::string read_string(const std::string &text, p_position_struct_ptr pos)
{
   // TODO: Implement the esc-char sequence stuff!
   std::string result = "";
   while ((pos->index < text.length()) && (text[pos->index] != '"')) {
      if (text[pos->index] == '\\') {
         inc(text, pos);
         result += read_esc_sequence(text, pos);
      } else {
         result += text[pos->index];
         inc(text, pos);
      }
   }

   // skipping the last "
   if ((pos->index < text.length()) && (text[pos->index] == '"')) {
      inc(text, pos);
   } else {
      // throwing an exception
      throw error_struct_create("String termination error!");
   }

   return result;
}

// -----------------------------------------------------------------------------

p_token_type ident_to_token_type(const std::string &ident)
{
   if (ident == "var") return TOK_VAR;
   if (ident == "begin") return TOK_BEGIN;
   if (ident == "end") return TOK_END;
   if (ident == "template") return TOK_TEMPLATE;
   if (ident == "program") return TOK_PROGRAM;
   if (ident == "unsigned") return TOK_UNSIGNED;
   if (ident == "signed") return TOK_SIGNED;
   if (ident == "byte") return TOK_BYTE;
   if (ident == "word") return TOK_WORD;
   if (ident == "dword") return TOK_DWORD;
   if (ident == "tbyte") return TOK_TBYTE;
   if (ident == "record") return TOK_RECORD;
   if (ident == "precondition") return TOK_PRECONDITION;
   if (ident == "read_only") return TOK_READ_ONLY;
   if (ident == "postcondition") return TOK_POSTCONDITION;
   if (ident == "expression") return TOK_EXPRESSION;
   if (ident == "and") return TOK_AND;
   if (ident == "nand") return TOK_NAND;
   if (ident == "or") return TOK_OR;
   if (ident == "nor") return TOK_NOR;
   if (ident == "xor") return TOK_XOR;
   if (ident == "function") return TOK_FUNCTION;
   if (ident == "proto") return TOK_PROTO;
   if (ident == "attributes") return TOK_ATTRIBUTES;
   if (ident == "pascal") return TOK_PASCAL;
   if (ident == "naked") return TOK_NAKED;
   if (ident == "cdecl") return TOK_CDECL;
   if (ident == "stdcall") return TOK_STDCALL;
   if (ident == "dllimport") return TOK_DLLIMPORT;
   if (ident == "extern") return TOK_EXTERN;
   if (ident == "nil") return TOK_NIL;
   return TOK_IDENT;
}

p_token_struct next_token(const std::string &text, p_position_struct_ptr pos)
{
   read_spaces(text, pos);
   if (pos->index < text.length()) {
      switch (text[pos->index])
      {
      // -------------------------- OPERATORS ----------------------------------
      case '+':
         return token_struct_create(TOK_ADD, inc(text, pos));
      case '-':
      {
         p_position_struct temp_pos = inc(text, pos);
         if ((pos->index < text.length()) && (text[pos->index] == '-')) {
            return token_struct_create(TOK_SUB_SUB, inc(text, pos));
         }
         return token_struct_create(TOK_SUB, temp_pos);
      }
      case '/':
         return token_struct_create(TOK_DIV, inc(text, pos));
      case '*':
         return token_struct_create(TOK_MUL, inc(text, pos));
      case '.':
         return token_struct_create(TOK_POINT, inc(text, pos));
      case ';':
         return token_struct_create(TOK_SEMICOLON, inc(text, pos));
      case ':':
         return token_struct_create(TOK_DPOINT, inc(text, pos));
      case '@':
         return token_struct_create(TOK_ADDR, inc(text, pos));
      case '(':
         return token_struct_create(TOK_RBRO, inc(text, pos));
      case ')':
         return token_struct_create(TOK_RBRC, inc(text, pos));
      case '[':
         return token_struct_create(TOK_EBRO, inc(text, pos));
      case ']':
         return token_struct_create(TOK_EBRC, inc(text, pos));
      case '{':
         return token_struct_create(TOK_CBRO, inc(text, pos));
      case '}':
         return token_struct_create(TOK_CBRC, inc(text, pos));
      case '|':
         return token_struct_create(TOK_VBAR, inc(text, pos));
      case ',':
         return token_struct_create(TOK_COLON, inc(text, pos));
      case '^':
         return token_struct_create(TOK_POWER, inc(text, pos));
      case '!':
         return token_struct_create(TOK_EXCLAMATION_MARK, inc(text, pos));
      case '=': // tokens parsed: =, ==
      {
         p_position_struct temp_pos = inc(text, pos);
         if ((pos->index < text.length()) && (text[pos->index] == '=')) {
            return token_struct_create(TOK_EQ, inc(text, pos));
         }
         return token_struct_create(TOK_ASSIGN, temp_pos);
      }
      case '<': // tokens parsed: <, <=, <>
      {
         p_position_struct temp_pos = inc(text, pos);
         if (pos->index < text.length()) {
            switch (text[pos->index])
            {
            case '=':
               return token_struct_create(TOK_LE, inc(text, pos));
            case '>':
               return token_struct_create(TOK_NE, inc(text, pos));
            }
         }
         return token_struct_create(TOK_LT, temp_pos);
      }
      case '>': // tokens parsed: >, >=
      {
         p_position_struct temp_pos = inc(text, pos);
         if ((pos->index < text.length()) && (text[pos->index] == '=')) {
            return token_struct_create(TOK_GE, inc(text, pos));
         }
         return token_struct_create(TOK_GT, temp_pos);
      }
      case '#': // comments
      {
         p_position_struct temp = position_struct_create(*pos);
         read_until_eoln(text, pos);
         return token_struct_create(TOK_IGNORE, temp);
      }
      case '"':
      {
         p_position_struct temp = position_struct_create(*pos);
         (void) inc(text, pos);
         std::string string_content = read_string(text, pos);
         return token_struct_create(TOK_STRING, string_content, temp);
      }
      default:
         // in this section everything else is parsed such as numbers, idents,...
         if (is_num(text[pos->index])) {
            // numbers
         } else if (is_alpha(text[pos->index])) {
            // everything else
            std::string ident = read_identifier(text, pos);
            return token_struct_create(ident_to_token_type(ident), ident, *pos);
         } else {
            // TODO: Reworking the message system!
            throw error_struct_create("Unknown token found", "", pos->index,
                                      pos->col);
         }
      }
   }
   return token_struct_create();
}

p_token_struct_list_ptr tokenize(const std::string &text,
                                 p_position_struct_ptr pos)
{
   // TODO: For performance reasons: Preallocate memory for vectors.
   p_token_struct_list_ptr result = new p_token_struct_list();
   p_token_struct curr_tok;

   while ((curr_tok = next_token(text, pos)).type != TOK_EOF) {
      // ignoring comments and other TOK_IGNORE flagged tokens
      if (curr_tok.type != TOK_IGNORE) result->push_back(curr_tok);
   }

   return result;
}

p_token_struct_list_ptr tokenize(const std::string &file_content)
{
   p_position_struct index = position_struct_create();
   return tokenize(file_content, &index);
}

